local setmetatable = setmetatable
local unpack = unpack
local select = select
local pairs = pairs
local ipairs = ipairs
local clock = os.clock
local open = io.open
local format = string.format
local print = print

module("profile",package.seeall)

local data = setmetatable({}, {
	__index = function(table, key)
		local v = {
			time = 0,
			enter = 0,
			leave = 0,
		}
		table[key] = v
		return v
	end
})

do
	local wraps = setmetatable({}, { __mode="kv" })

	function Wrap(name, func)
		local f = wraps[func]
		if f then return f end
		
		local v = data[name]
		local s
		
		local after = function (...)
			local time = clock() - s
			v.leave = v.leave + 1
			v.time = v.time + time
			return ...
		end
		
		local before = function (...)
			v.enter = v.enter + 1
			s = clock()
			return after(func(...))
		end
		wraps[func] = before
		wraps[before] = before -- prevents self wrapping
		return before
	end
end

do
	local debug_times, debug_stack = {}, {}

	local function debug_hook(event)
		local t = clock()
		if event == "call" then
			local info = debug.getinfo(2)
			local key = ("[%q]:%d:%s"):format(info.source, info.currentline, info.name or "*anonymous*")
			local index = #debug_stack + 1
			debug_stack[index] = { key = key, time = t, ctime = 0 }
			if not debug_times[key] then
				local simulated = (debug.getfenv(info.func) == api.global._G)
				debug_times[key] = { time = 0, count = 0, ctime = 0, source = info.source, line = info.linedefined, name = info.name or "*anonymous*", simulated = simulated and "true" or "false" }
			end
		elseif event == "return" or event == "tail return" then
			local index = #debug_stack
			if index == 0 then return end
			local i = debug_stack[index]
			debug_stack[index] = nil
			t = t - i.time
			local rt = t - i.ctime
			for i = 1, index - 1 do
				local v = debug_stack[i]
				v.ctime = v.ctime + rt
			end
			local v = debug_times[i.key]
			v.time = v.time + t
			v.count = v.count + 1
			v.ctime = v.ctime + i.ctime
		end
	end

	function Profile(handler)
		local sethook = debug.sethook
		sethook(debug_hook, "rc")
		handler()
		sethook()
		local f = open("profile.log", "w")
		if #debug_stack > 0 then
			log("EE profile stack not empty:")
			for index, entry in ipairs(debug_stack) do
				log(("\t%d\t%s"):format(index, entry.key))
			end
		end
		for _, v in pairs(debug_times) do
			f:write(("%s\t%s\t%d\t%f\t%f\t%s\n"):format(("%s:%d"):format(v.source or "<>", v.line or -1), v.name, v.count, v.time, v.time - v.ctime, v.simulated))
		end
		f:close()
	end
end

function Dump()
	local f = open("profile.txt", "w")
	f:write("Name\tTime\tInvocations\tLeft\n")
	for name, value in pairs(data) do
		f:write(format("%s\t%s\t%d\t%d\n", name, format("%.3f", value.time):gsub("%.", ","), value.enter, value.enter - value.leave))
	end
	f:close()
end
